package com.gecko.flutter_screen_capture;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.DisplayMetrics;
import android.util.Log;

import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;

public class ScreenCapture {

    private static final String TAG = "ScreenCapture";

    public static final int REQUEST_MEDIA_PROJECTION = 100;

    protected Activity mActivity;
    protected int mScreenDensity;
    protected int mWidth;
    protected int mHeight;
    protected ImageListener mListener;
    protected StateListener mStateListener;
    private boolean mIsRunning = false;

    private static Intent mResultData;

    private ImageReader mImageReader;
    private MediaProjection mMediaProjection;
    private VirtualDisplay mVirtualDisplay;
    private MediaProjectionManager mMediaProjectionManager;
    private Handler mHandler;
    private boolean isMumu = false;

    private static ScreenCapture sInstance;

    public interface ImageListener {
        public void onImageAvailable(Bitmap bmp);
    }

    public interface StateListener {
        public void onStateChanged(boolean state);
    }

    public static ScreenCapture inst(Activity activity) {
        if(sInstance == null)
            sInstance = new ScreenCapture(activity);
        return sInstance;
    }

    private ScreenCapture(Activity activity) {
        mActivity = activity;
        DisplayMetrics metrics = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
        mScreenDensity = metrics.densityDpi;
        mWidth = metrics.widthPixels;
        mHeight = metrics.heightPixels;
        setIsRunning(false);
        Log.i(TAG, "display metrics w:" + mWidth + ", h:" + mHeight + ", dpi:" + mScreenDensity);

        // create capture handling thread
        HandlerThread thread = new HandlerThread("screencap");
        thread.start();
        mHandler = new Handler(thread.getLooper());

        mMediaProjectionManager = (MediaProjectionManager)
                activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);

        try {
            Class clz = Class.forName("android.os.SystemProperties");
            Method getMethod = clz.getDeclaredMethod("get", String.class, String.class);
            String model = (String)getMethod.invoke(null, "ro.product.model", "hello");
            if (model.equalsIgnoreCase("mumu")) {
                isMumu = true;
                mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGB_888, 2);
            } else {
                mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 2);
            }
        } catch (Exception e) {
            mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 2);
        }
        mImageReader.setOnImageAvailableListener(new ImageAvailableListener(), mHandler);
    }

    private SharedPreferences getSharedPreferences() {
        return mActivity.getSharedPreferences("ScreenCap", Context.MODE_PRIVATE);
    }

    public static void setResultData(Intent intent) {
        mResultData = intent;
        if(sInstance != null)
            sInstance.startInternal();
    }


    private void setUpMediaProjection() {
        mMediaProjection = mMediaProjectionManager.getMediaProjection(Activity.RESULT_OK, mResultData);
        // register media projection stop callback
        mMediaProjection.registerCallback(new MediaProjectionStopCallback(), mHandler);
    }

    private void tearDownMediaProjection() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mMediaProjection != null) {
                    Log.i(TAG, "stop MediaProjection");
                    mMediaProjection.stop();
                    mMediaProjection = null;
                }
            }
        });
    }

    private void startInternal() {
        if(mMediaProjection != null && mVirtualDisplay != null)
            return;
        Log.i(TAG, "startInternal()");
        if (mMediaProjection != null) {
            setUpVirtualDisplay();
        } else if (mResultData != null) {
            setUpMediaProjection();
            setUpVirtualDisplay();
        } else {
            Log.e(TAG, "you don't have screen capture permission!");
            return;
        }
    }


    public boolean start() {
        if(isRunning())
            return true;
        setIsRunning(true);
        Log.i(TAG, "start(): mResultData:" + mResultData);
        if(mResultData == null) {
            MediaProjectionManager mpm = (MediaProjectionManager)mActivity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
            mActivity.startActivityForResult(mpm.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
            return false;
        } else {
            startInternal();
            return true;
        }
    }

    private void setUpVirtualDisplay() {
        Log.i(TAG, "Setting up a VirtualDisplay: " +  mWidth + "x" + mHeight +  " (" + mScreenDensity + ")");
        mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture",
                mWidth, mHeight, mScreenDensity,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                mImageReader.getSurface(), null, mHandler);
    }

    public void stop() {
        setIsRunning(false);
        if (mVirtualDisplay == null) {
            return;
        }
        Log.i(TAG, "release VirtualDisplay");
        mVirtualDisplay.release();
        mVirtualDisplay = null;
        mResultData = null;
        tearDownMediaProjection();
    }

    private class ImageAvailableListener implements ImageReader.OnImageAvailableListener {
        private long mLastCheckTime = 0;
        @Override
        public void onImageAvailable(ImageReader reader) {
            long curTime = System.currentTimeMillis();

            Image image = null;
            FileOutputStream fos = null;
            Bitmap bitmap = null;

            try {
                image = reader.acquireLatestImage();
                if (image != null) {
                    if(curTime - mLastCheckTime < 300)
                        return;
                    Image.Plane[] planes = image.getPlanes();
                    ByteBuffer buffer = planes[0].getBuffer();
                    int pixelStride = planes[0].getPixelStride();
                    int rowStride = planes[0].getRowStride();
                    int rowPadding = rowStride - pixelStride * mWidth;

                    if(isMumu) {
                        byte[] bytes = new byte[mWidth*mHeight*3];
                        buffer.get(bytes);
                        int[] colors = new int[mWidth*mHeight];
                        for(int y = 0; y < mHeight; y++) {
                            for(int x = 0; x < mWidth; x++) {
                                int pos = y*mWidth+x;
                                colors[pos] = bytes[pos*3+2] + (bytes[pos*3+1]<<8) + (bytes[pos*3]<<16) + 0xff000000;
                            }
                        }
                        bitmap = Bitmap.createBitmap(colors, mWidth, mHeight, Bitmap.Config.ARGB_8888);
                        //bitmap.compress(Bitmap.CompressFormat.JPEG, 90, new FileOutputStream("/sdcard/cap.jpg"));
                    } else {
                        // create bitmap
                        bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888);
                        bitmap.copyPixelsFromBuffer(buffer);
                        bitmap = Bitmap.createBitmap(bitmap, 0, 0, mWidth, mHeight);    // remove padding
                    }

                    if(mListener != null)
                        mListener.onImageAvailable(bitmap);

                    //writeBitmap(bitmap);
                    mLastCheckTime = curTime;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
                }

                if (bitmap != null) {
                    bitmap.recycle();
                }

                if (image != null) {
                    image.close();
                }
            }
        }
    }

    private class MediaProjectionStopCallback extends MediaProjection.Callback {
        @Override
        public void onStop() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, "stopping projection.");
                    if (mVirtualDisplay != null)
                        mVirtualDisplay.release();
                    if (mImageReader != null)
                        mImageReader.setOnImageAvailableListener(null, null);
                    if(mMediaProjection != null)
                        mMediaProjection.unregisterCallback(MediaProjectionStopCallback.this);
                    mVirtualDisplay = null;
                    mMediaProjection = null;
                }
            });
        }
    }

    public void setImageListener(ImageListener listener) {
        mListener = listener;
    }

    public void setStateListener(StateListener listener) {
        mStateListener = listener;
    }

    public Activity getActivity() {
        return mActivity;
    }

    public synchronized boolean isRunning() {
        return mIsRunning;
    }

    public synchronized void setIsRunning(boolean flag) {
        boolean oldFlag = mIsRunning;
        mIsRunning = flag;
        if(oldFlag != flag && mStateListener != null)
            mStateListener.onStateChanged(flag);
    }
}
